﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Marmat.Common.Http.Attributes;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;


namespace Marmat.Common.Http
{
    class HttpApiExecuteActionFilterAttribute : ActionFilterAttribute
    {
        private readonly LogMessage logMessage;

        public HttpApiExecuteActionFilterAttribute(LogMessage logMessage)
        {
            this.logMessage = logMessage;
        }

        private bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
            //return cert.GetCertHashString() == "xxxxxxxxxxxxxxxx";
        }


        public override void OnActionExecuting(ApiActionContext context)
        {
            var method = context.ActionDescriptor.Method;
            var parameters = method.GetParameters();

            // 是否在客户端验证服务器SSL/TLS证书
            if (context.HttpApiConfig.EnableServerCertificateValidation)
                context.Request.ServerCertificateValidationCallback += RemoteCertificateValidationCallback;

            bool isBody = false;
            int parameterLength = parameters.Where(x => !context.ActionDescriptor.ignoreArgumentCollection.Contains(x.Name)).Count();

            if (parameterLength > 0)
            {
                foreach (var parameter in parameters)
                {
                    if (parameter.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(FromBodyAttribute) || x.AttributeType == typeof(FromFormAttribute) || x.AttributeType == typeof(FormContentAttribute)) is not null)
                    {
                        isBody = true;
                        break;
                    }
                }
            }

            if (isBody && context.Request.Method == "POST")
            {
                string body = string.Empty;
                byte[] postData;

                bool form_data = parameters.FirstOrDefault(parameter => parameter.GetCustomAttribute<FromFormAttribute>() is not null) is not null;
                bool www_form_urlencoded = parameters.FirstOrDefault(parameter => parameter.GetCustomAttribute<FormContentAttribute>() is not null) is not null;

                var assemblyInfo = this.GetType().Assembly.GetName();
                context.Request.UserAgent = $"{assemblyInfo.Name}/{assemblyInfo.Version}";

                if (form_data && context.ActionDescriptor.Arguments[0] is FormCollection formCollection)
                {
                    StringBuilder builder = new StringBuilder();
                    using System.IO.Stream reqStream = context.Request.GetRequestStream();
                    string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
                    byte[] boundarybytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
                    context.Request.ContentType = "multipart/form-data; boundary=" + boundary;

                    // 普通参数模板
                    string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
                    //带文件的参数模板
                    string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";

                    foreach (var item in formCollection.Keys)
                    {
                        builder.AppendLine($"KEY={item.Key}[Text]；VALUE={item.Value}");

                        reqStream.Write(boundarybytes, 0, boundarybytes.Length);
                        string formitem = string.Format(formdataTemplate, item.Key, item.Value);
                        byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
                        reqStream.Write(formitembytes, 0, formitembytes.Length);
                    }

                    foreach (var file in formCollection.Files)
                    {
                        builder.AppendLine($"KEY={file.Name}[File]；VALUE={file.FileName}");

                        reqStream.Write(boundarybytes, 0, boundarybytes.Length);
                        string header = string.Format(headerTemplate, file.Name, file.Name, file.ContentType);
                        byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
                        reqStream.Write(headerbytes, 0, headerbytes.Length);
                        using (var fileStream = new FileStream(file.FileName, FileMode.Open, FileAccess.Read))
                        {
                            byte[] buffer = new byte[4096];
                            int bytesRead = 0;
                            long total = 0;
                            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                            {
                                reqStream.Write(buffer, 0, bytesRead);
                                total += bytesRead;
                            }
                        }
                    }
                    body = builder.ToString();
                    byte[] trailer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
                    reqStream.Write(trailer, 0, trailer.Length);
                    reqStream.Close();
                }
                else if (www_form_urlencoded)
                {
                    var buff = new StringBuilder(string.Empty);
                    var arguments = context.ActionDescriptor.Arguments;

                    for (int index = 0; index < parameters.Length; index++)
                    {
                        var parameter = parameters[index];

                        //如果参数已被标注为忽略，则在此处放弃。
                        if (context.ActionDescriptor.ignoreArgumentCollection.Contains(parameter.Name))
                            continue;

                        string name = parameter.Name;
                        string value = JsonConvert.SerializeObject(arguments[index]);
                        if (parameter.GetCustomAttribute<AliasAsAttribute>() is AliasAsAttribute alias)
                            name = alias.AliasName;
                        buff.Append($"{WebUtility.UrlEncode(name)}={WebUtility.UrlEncode(value)}&");
                    }
                    body = buff.ToString().TrimEnd('&');
                    postData = Encoding.UTF8.GetBytes(body);

                    context.Request.ContentType = "application/x-www-form-urlencoded";
                    context.Request.ContentLength = postData?.Length ?? 0;
                    using System.IO.Stream reqStream = context.Request.GetRequestStream();
                    if (postData.Length > 0)
                    {
                        reqStream.Write(postData, 0, postData.Length);
                        reqStream.Close();
                    }
                }
                else
                {
                    Encoding encoding = new UTF8Encoding();
                    var arguments = context.ActionDescriptor.Arguments;
                    if (parameterLength == 1)
                        body = Newtonsoft.Json.JsonConvert.SerializeObject(arguments[0]);
                    else if (parameterLength > 0)
                    {
                        JObject args = new();
                        for (int index = 0; index < parameters.Length; index++)
                        {
                            var parameter = parameters[index];

                            //如果参数已被标注为忽略，则在此处放弃。
                            if (context.ActionDescriptor.ignoreArgumentCollection.Contains(parameter.Name))
                                continue;

                            string name = parameter.Name;
                            string value = JsonConvert.SerializeObject(arguments[index]);
                            if (parameter.GetCustomAttribute<AliasAsAttribute>() is AliasAsAttribute alias)
                                name = alias.AliasName;
                            args[name] = Newtonsoft.Json.JsonConvert.DeserializeObject<JToken>(value);
                        }
                        body = JsonConvert.SerializeObject(args);
                    }
                    postData = encoding.GetBytes(body);
                    context.Request.ContentType = "application/json";
                    context.Request.ContentLength = postData?.Length ?? 0;
                    using System.IO.Stream reqStream = context.Request.GetRequestStream();
                    if (postData.Length > 0)
                    {
                        reqStream.Write(postData, 0, postData.Length);
                        reqStream.Close();
                    }
                }
                logMessage.RequestString = body;
            }
            else
                context.Request.ContentLength = 0;
        }
    }
}
